#include "console-common.h"
#include "lua-fn-aliases.h"
#include <string>
#include <cstdlib>
#include <cstddef>
#include <cstring>
// #include <iostream>

char hex_str[] = "0123456789abcdef";
char escaped_str[] = "abtnvfr";
char unesc_table[32] = {
	 0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
};

char* PartialUCS2UTF8 (int num)
{
	static char str[8] = {0};
	char mask = 0x3F;
	int i;
	for (i = 6; num & ~mask; i--)
	{
		str[i] = (num & 0x3F) | 0x80;
		num = (unsigned)num >> 6;
		mask >>= 1;
	}
	str[i] = ((~mask<<1) & 0xFF) | num;
	return &str[i];
}

int luastr_escape (lua_State * l)
{
	int args = lua_gettop(l), i, curr_chr1;
	size_t str_length;
	if (args < 1)
		return luaL_error(l, "Must be least one argument");
	static char hexesc_str[5] = "\\x\0\0";
	static char literal_esc[3] = "\\\0";
	const char* input_str = luaL_checklstring(l, 1, &str_length);
	std::string result;
	std::string::size_type require_size = (str_length + 1) * 4;
	result.reserve(require_size);
	for (i = 0; i < (int)str_length; i++)
	{
		curr_chr1 = input_str[i];
		switch (curr_chr1)
		{
		case '\0':
			if (input_str[i+1] >= '0' && input_str[i+1] <= '9')
				result += "\\x00";
			else
				result += "\\0";
			break;
		case '"':
			result += "\\\"";
			break;
		case '\\':
			result += "\\\\";
			break;
		default:
			if (curr_chr1 < 0x7F)
			{
				if (curr_chr1 >= 0x20)
				{
					result += input_str[i];
					break;
				}
				if (curr_chr1 >= 0x07 && curr_chr1 <= 0x0D)
				{
					literal_esc[1] = escaped_str[curr_chr1 - 7];
					result += literal_esc;
					break;
				}
			}
			hexesc_str[2] = hex_str[(curr_chr1>>4)&0xF];
			hexesc_str[3] = hex_str[curr_chr1 & 0xF];
			result += hexesc_str;
		}
	}
	lua_pushstring(l, result.c_str());
	return 1;
}

int luastr_unescape (lua_State * l)
{
	int args = lua_gettop(l), h = 0, i, ii;
	size_t str_length;
	if (args < 1)
		return luaL_error(l, "Must be least one argument");
	const char* input_str = luaL_checklstring(l, 1, &str_length);
	std::string result;
	char curr_chr1, esc_sum, j;
	std::string::size_type require_size = str_length;
	result.reserve(require_size);
	for (i = 0; i < (int)str_length; i++)
	{
		if (input_str[i] != '\\')
		{
			result += input_str[i];
			continue;
		}
		i++;
		curr_chr1 = input_str[i];
		switch (curr_chr1)
		{
		case 'a':
			result += '\x07';
			break;
		case 'b':
			result += '\b';
			break;
		case 'e':
			result += '\x1b';
			break;
		case 'f':
			result += '\f';
			break;
		case 'n':
			result += '\n';
			break;
		case 'r':
			result += '\r';
			break;
		case 't':
			result += '\t';
			break;
		case 'u':
			if (input_str[i+1] != '{') goto other_case;
			for (ii = i + 2; input_str[ii] && input_str[ii] != '}'; ii++)
			{
				h = (h << 4) | unesc_table[input_str[ii] & 0x1F];
			}
			if (h >= 0 && h < 0x80)
				result += (char)h;
			else
				result += PartialUCS2UTF8(h);
			// std::cout << h << std::endl;
			i = ii; h = 0;
			break;
		case 'v':
			result += '\x0B';
			break;
		case 'x':
			i += 2;
			result += (char)(
				(unesc_table[input_str[i-1]&0x1F]<<4)|
				unesc_table[input_str[i]&0x1F]
			);
			break;
		case 'z': // skip whitespace
			while ((input_str[i+1] >= '\t' && input_str[i+1] <= '\r') || input_str[i+1] <= ' ') i++;
			break;
		default:
		other_case:
			if (curr_chr1 >= '0' && curr_chr1 <= '9')
			{
				esc_sum = curr_chr1 - '0';
				for (j = 2; j > 0; j--)
				{
					curr_chr1 = input_str[i+1];
					if (curr_chr1 >= '0' && curr_chr1 <= '9')
					{
						esc_sum = esc_sum * 10 + curr_chr1 - '0';
						i++;
					}
				}
				result += (char)(esc_sum);
			}
			else
				result += curr_chr1;
		}
	}
	lua_pushlstring(l, result.data(), result.length());
	return 1;
}

int luastr_align (lua_State * l)
{
	int offset = 0;
	size_t src_len;
	const char* src = luaL_checklstring(l, 1, &src_len);
	int align_mode = luaL_checkint(l, 2);
	int max_len = luaL_checkint(l, 3);
	char fill_chr = luaL_optint(l, 4, ' ');
	char* dst = (char*) malloc (max_len);
	if (!dst)
		return luaL_error(l, "out of memory");
	memset(dst, fill_chr, max_len);
	int len = ((int)src_len < max_len) ? src_len : max_len;

	if (align_mode == 0)
		offset = (max_len - len) >> 1;
	else if (align_mode > 0)
		offset = max_len - len;
	
	memcpy(dst + offset, src, len);

	lua_pushlstring(l, dst, max_len);
	
	free(dst);
	return 1;
}

int luastr_bracket_quote (lua_State * l)
{
	size_t sz;
	int i, eqnum = 0, maxeqnum = 0;
	const char* src = luaL_checklstring(l, 1, &sz);
	for (i = sz - 1; i >= 0; i--)
	{
		if (src[i] == '=') eqnum++;
		else if (maxeqnum < eqnum) maxeqnum = eqnum;
	}
	if (maxeqnum < eqnum) maxeqnum = eqnum;
	eqnum ++;
	char* surround_str = (char*)malloc(eqnum + 4);
	if (surround_str)
	{
		surround_str[0] = '[';
		surround_str[eqnum+1] = '[';
		surround_str[eqnum+2] = '\n';
		surround_str[eqnum+3] = '\0';
		memset(surround_str+1, '=', eqnum);
		lua_pushstring(l, surround_str);
		lua_pushvalue(l, -2);
		surround_str[0] = ']';
		surround_str[eqnum+1] = ']';
		surround_str[eqnum+2] = '\0';
		lua_pushstring(l, surround_str);
		lua_concat(l, 3);
	}
	else
		luaL_error(l, "out of memory");
	return 1;
}

int luastr_range_expand (lua_State * L)
{
	int a, b, inc, len, tmp_len;
	char *expanded_str, *tmp_str;
	a = lua_tointeger(L, 1);
	b = lua_tointeger(L, 2);
	inc = luaL_optint(L, 3, 1);
	if ((a < b) == (inc < 0)) 
		inc = -inc;
	if (inc == 0) return luaL_error(L, "argument #3 must be non-zero");
	len = (b - a) / inc + 1; expanded_str = (char*)malloc(len);
	if (!expanded_str) return luaL_error(L, "out of memory");
	tmp_str = expanded_str; tmp_len = len;
	while (tmp_len--)
		*tmp_str = (a & 0xFF), a += inc, tmp_str++;
	lua_pushlstring(L, expanded_str, len);
	return 1;
}

int luastr_expand_env (lua_State * L)
{
	int i; bool memfail = false;
	intptr_t * save1;

	for (i = 0; i < REG_MAX_LUA_STATES; i++)
	{
		if (luacon_stateRegTable[2*i] == (intptr_t)L)
		{
			save1 = &(luacon_stateRegTable[2*i+1]);
			break;
		}
	}
	
	size_t sz, dstl, ssz;
	const char* src = luaL_checklstring(L, 1, &sz);
	luaL_checktype(L, 2, LUA_TTABLE);
	int extsz = sz + 128;
	char* dst = (char*)malloc(extsz);
	*save1 = (intptr_t)dst;
	
	if (dst != NULL)
	{
		for (i = 0, dstl = 0; i < (int)sz;)
		{
			if (src[i] == '%')
			{
				i++;
				const char* next = strchr(&src[i], '%');
				if (next == &src[i])
				{
					i++; dst[dstl++] = '%'; continue;
				}
				else if (next == NULL)
				{
					memfail = true; break;
				}
				lua_pushlstring(L, &src[i], (size_t)(next - &src[i]));
				lua_rawget(L, 2);
				const char* envs = lua_tolstring(L, -1, &ssz);
				if (envs != NULL)
				{
					while ((int)(dstl + ssz + sz) > extsz)
					{
						extsz *= 2;
						void* tmp = realloc(dst, extsz);
						if (tmp == NULL)
						{
							memfail = true; break;
						}
						dst = (char*)tmp;
					}
					if (memfail)
						break;
					memcpy(&dst[dstl], envs, ssz);
					dstl += ssz;
				}
				i = (int)(next - src + 1); lua_pop(L, 1);
			}
			else
				dst[dstl++] = src[i++];
		}
	lua_pushlstring(L, dst, dstl);
	}

	free (dst);
	*save1 = (intptr_t)NULL;
	return memfail ? luaL_error(L, "expanding string error") : 1;
}

int luacon_extend_str_api(lua_State *L)
{
	
	lua_getglobal(L, "string");
	const static struct luaL_Reg string_ext_api [] {
		{"escape", luastr_escape},
		{"unescape", luastr_unescape},
		{"align", luastr_align},
		{"bracket_quote", luastr_bracket_quote},
		{"range_expand", luastr_range_expand},
		{"expand_env", luastr_expand_env},
		{NULL, NULL}
	};
	const static struct { const char* name; int value; } string_api_ints[] = {
		{"LEFT_ALIGN", -1}, {"CENTER_ALIGN", 0}, {"RIGHT_ALIGN", 1}, {NULL, 0}
	};
	luaL_set_cfuncs(L, string_ext_api);
	luaL_va_setfield_ints(L, string_api_ints);
	lua_setglobal(L, "str");
	return 0;
}
